package org.semanticflash.rdf.values
{
	
	
	import com.adobe.crypto.MD5;
	import com.adobe.net.URI;
	
	import mx.utils.UIDUtil;
	
	
	
	
	/**
	 * 
	 * 
	 * TODO: 
	 * 		We are not correctly splitting the bnode ns ( bNodes instance appear with no namespace/local separation )
	 * 		Tried to fix this but resulted in an infinite recursion. Has something to do with static initializer sequence.
	 * 		Anyway, there is no aparent need to have this setup right so for now we'll just forget about it.
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */
	public class IRI extends RDFValue
	{
		
		
		
		
		
		public static const variableNS	:IRI = IRI.instance( "urn:semanticFlashVariable:" );
		

		public static const bNodeNS		:IRI = IRI.instance( "_:" );		
		
		
		
		
		/**
		 * 
		 * The Wildcard is a special IRI used within the framework to create 
		 * pattern triples.
		 * 
		 * We use this instead of nulls so we can share algorithms between triples and triple patterns.
		 * 
		 * You SHOULD not state a pattern triple in a graph, but you can index it,
		 * look for redirections, etc.
		 * 
		 */		
		public static const WILDCARD:IRI = bNode( );		
		
		
		
		
		
		
		
		

		
		private var _stringForm:String;
		/**
		 * 
		 * Returns the string form of an IRI
		 * 
		 * @return 
		 * 
		 */		
		public function get stringForm( ):String
		{
			if ( _ns ) // we have namespace and local defined
				return _ns.stringForm + localName;		
			return _stringForm;					
		}
		
		

		private var _ns:IRI;
		
		public function get ns():IRI
		{
			return _ns;						
		}
		


		private var _localName:String;
		public function get localName():String
		{
			return _localName;
		}
		

		
		private var _isBNode:int=0; // 0: not computed, 1: yes, 2: no
		public function get isBNode():Boolean
		{
			if ( _isBNode == 0 )
				_isBNode = ( stringForm.indexOf( "_:" ) == 0 ) ? 1 : 2;
			return ( _isBNode == 1 );		
		}
		
		
		
		
		public function get isVariable( ):Boolean
		{
			return ( ns == variableNS );		
		}
		
		
		
		
		
		
		public function get hasFragment( ):Boolean
		{
			return asURI.fragment.length > 0;		
		}
		
		

		/**
		 * 
		 * Not all IRIs can be dereferenced,
		 * Only HTTP and HTTPS for now ( or forever?? )
		 * 
		 * @return 
		 * 
		 */
		public function get isDereferencable( ):Boolean
		{
			var scheme:String = asURI.scheme;
			return ( scheme == "http" || scheme == "https" )
		}










		/**
		 * 
		 * If this iri has a fragment ( hash ), then this property will return the fragment-less 
		 * version of the IRI.
		 * The pound ( # ) sign is removed as well.
		 * 
		 * If this IRI has no fragment, then this property will return this same instance.
		 * 
		 * @return 
		 * 
		 */		
		public function get noFragment( ):IRI
		{
			
			if ( ! hasFragment )
				return this;
			
			var str:String = stringForm;
			
			var strNoFrag:String = str.substr( 0, str.length - asURI.fragment.length - 1 );
			
			return instance( strNoFrag );
		}
		
		
		
		
		
			
		
		public function IRI( stringForm:String=null, ns:IRI=null, localName:String=null )
		{
			_ns = ns;
			_localName = localName;
			_stringForm = stringForm;
		}

		
//		/**
//		 * 
//		 * This IRI might have been created without a determined ns and localName.
//		 * 
//		 * @param ns
//		 * @param localName
//		 * 
//		 */		
//		internal function setNSAndLocalName( ns:IRI, localName:String ):void
//		{			
//			_ns = ns;
//			_localName = localName;		
//		
//		}	

		
		// cache for performance
		private var _nt:String;
		
		override public function get nt():String
		{
			if ( !_nt )
				_nt = "<" + stringForm + ">";
			return _nt;
		
		}
		
		
		

		
		
		private var _asURI:URI;
		/**
		 * 
		 * TODO: this is just using a dumb toString() internally.
		 * We need to implement the real algorithm
		 * 
		 * ( you can find it in the IRI spec )
		 * 
		 * @return 
		 * 
		 */		
		public function get asURI( ):URI
		{
			if ( ! _asURI )
				_asURI = new URI( this.stringForm );
			return _asURI;
		}
		
		
		
		
		
		
		private var _asNamespace:Namespace;
		/**
		 * TODO:
		 * Not every IRI can be a Namespace. This is BROKEN.
		 * 
		 * @return 
		 * 
		 */		
		public function get asNamespace( ):Namespace
		{
			if ( !_asNamespace )
				_asNamespace = new Namespace( this.stringForm );
			return _asNamespace;		
		}
		
		
		
		
		
		
		
		
		
		
		
		private static function splitNSAndLocal( stringForm:String ):Array
		{
			var uri:URI = new URI( stringForm );
			
			if( !uri )
				return [ stringForm ];
			
			if( uri.scheme.toLowerCase() == "https" || uri.scheme.toLowerCase() == "http" )
			{

				for( var index:int = stringForm.length ; index > uri.scheme.length + 3; index-- )
				{
					if( stringForm.charAt( index ) == '#' || stringForm.charAt( index ) == ':' || stringForm.charAt( index ) == '/' )
					{
						
						if ( index == stringForm.length - 1 ) // character found at the end of string. there is no localName left to the right
							break;
						
						return [ stringForm.substr( 0, index + 1 ), stringForm.substr( index + 1 ) ];
					}
				}

			}
			
			return [ stringForm ];
		}		
		
		
		
		
		
		
		
		
		
		
		
		
		
		

		
		
		
		
		///////////////////////////////////////////////////////////////////
		/////////////////////////// factories /////////////////////////////
		///////////////////////////////////////////////////////////////////
		
		
		
		/**
		 * 
		 * Variables are regular IRIs generated following a convention.
		 * "name" is appended to the variable namespace ( urn:variable: ).
		 * <pre>
		 *  
		 * IRI.variable( "price" ); // returns <urn:variable:price>
		 * 
		 * 
		 * </pre>
		 * 
		 * @param name
		 * @return 
		 * 
		 */		
		public static function variable( name:String ):IRI
		{
			return IRI.instance2( variableNS, name );					
		}
		
		
		
		

		/**
		 * 
		 * <b>Factory</b>
		 * BNodes are NOT allowed within the framework.
		 * They are indeed represented through IRIs in the bnode scheme ( _: )
		 * The generated IRI is guaranteed to be unique within one application session.
		 * ( Which is conformant to the BNode semantics )
		 * 
		 * <p>
		 * Note:</br>
		 * You can test if an IRI belongs to the BNode scheme by calling IRI::isBNode( ):Boolean
		 * </p>
		 * 
		 * <p>
		 * If you pass ( both ) a contextNS and a localID then the bnode name will be generated from a combination
		 * of these two. This is useful when you are parsing a document and need to refer to the same BNode twice.
		 * Pass the IRI of the document as contextNS ( or any other IRI, as long as it is unique to the document ) along
		 * with the actual ID used in the doc ( can be any string, it will MD5'ed inside ) and you can forget about
		 * keeping a local BNode directory.
		 * </p>
		 * 
		 * @param contextNS
		 * @param localID
		 * @return 
		 * 
		 */		 	
		public static function bNode( contextNS:IRI = null, localID:String = null ):IRI
		{
			
			var id:String = null;
			
			if ( contextNS != null && localID != null )
				id = contextNS.stringForm + " " + localID;							
			else
				id = UIDUtil.createUID( );
			
			return instance( "_:" + MD5.hash( id ) );		
		}		
		
		
		

		/**
		 * 
		 * <b>Factory</b>
		 * 
		 * 
		 * @param stringForm
		 * @return 
		 * 
		 */		
		public static function instance( stringForm:String ):IRI
		{

			if ( stringForm == null )
				return null;

			var key:String, cachedTerm:RDFValue;			
			
			key = "i_" + stringForm;
			
			cachedTerm = getCachedValue( key );
			
			if ( cachedTerm )
			{
				return cachedTerm as IRI;
			}
			
			
			// create a new IRI instance
			// there are two kinds of IRIs:
			// - those that can be split into namespace-local
			// - those that don't

			var parts:Array = splitNSAndLocal( stringForm );
			
			if ( parts.length == 2 ) // namespace and local
			{
				var ns:IRI = instance( parts[ 0 ] as String );
				var local:String = parts[ 1 ] as String;
				return cacheValue( key, new IRI( null, ns, local ) ) as IRI;
			}
			
			var stringFormIRI:IRI = new IRI( stringForm );
			
			// store in internal cache
			cacheValue( key, stringFormIRI );
			
			return stringFormIRI;

		}
		


		/**
		 * 
		 * <b>Factory</b>
		 * 
		 * @param ns
		 * @param localName
		 * @return 
		 * 
		 */
		public static function instance2( ns:IRI, localName:String ):IRI
		{
			
			// TODO: for now we are not optimizing, but we should
			return instance( ns.stringForm + localName );
		}
		
		
		
		/**
		 * 
		 * <b>Factory</b>
		 * 
		 * @param qname
		 * @return 
		 * 
		 */		
		public static function instance3( qname:QName ):IRI
		{
			// TODO: for now we are not optimizing, but we should
			return instance( qname.uri + qname.localName );		
		}
		
		
		
		
		/**
		 * 
		 * <b>Factory</b>
		 * 
		 * @param uri
		 * @return 
		 * 
		 */
		public static function instance4( uri:URI ):IRI
		{
			// TODO: for now we are not optimizing, but we should
			return instance( uri.toString() );
		}




		/**
		 * 
		 * <b>Factory</b>
		 * 
		 * @param ns
		 * @param localName
		 * @return 
		 * 
		 */
		public static function instance5( ns:Namespace, localName:String ):IRI
		{
			// TODO: for now we are not optimizing, but we			
			return instance( ns.uri + localName );		
		}


		
		
		public static function instance6( value:* ):IRI
		{

			if ( value is IRI )
				return value as IRI;	
			
			if ( value is String )
				return IRI.instance( String( value ) );
			
			if ( value is QName )
				return IRI.instance3( value as QName );		
			
			if ( value is Namespace )
				return IRI.instance( ( value as Namespace ).toString( ) );		
			
			if ( value is IRDFValueHolder )
				return ( value as IRDFValueHolder ).rdfValue as IRI;			
			
			
			return null;
		
		}


		
	}


}